import os
import re
import argparse
from typing import List, Optional


class FileContentReplacer:
    def __init__(self, root_dir: str, file_types: List[str], search_pattern: str,
                 replace_with: str, dry_run: bool = False, case_sensitive: bool = False):
        """
        初始化文件内容替换工具

        :param root_dir: 要搜索的根目录
        :param file_types: 要处理的文件扩展名列表(如['.txt', '.py'])
        :param search_pattern: 要搜索的正则表达式模式
        :param replace_with: 替换内容
        :param dry_run: 是否只显示将要做的更改而不实际修改
        :param case_sensitive: 是否区分大小写
        """
        self.root_dir = os.path.abspath(root_dir)
        self.file_types = [ext.lower() for ext in file_types]
        self.search_pattern = search_pattern
        self.replace_with = replace_with
        self.dry_run = dry_run
        self.flags = 0 if case_sensitive else re.IGNORECASE

        # 验证目录存在
        if not os.path.isdir(self.root_dir):
            raise ValueError(f"指定的目录不存在: {self.root_dir}")

    def run(self):
        """执行搜索和替换操作"""
        print(f"开始在目录 {self.root_dir} 中搜索...")
        print(f"文件类型: {', '.join(self.file_types)}")
        print(f"搜索模式: {self.search_pattern}")
        print(f"替换为: {self.replace_with}")
        print(f"模拟运行: {'是' if self.dry_run else '否'}")
        print("=" * 60)

        total_files = 0
        total_matches = 0
        total_replacements = 0

        for root, _, files in os.walk(self.root_dir):
            for filename in files:
                file_ext = os.path.splitext(filename)[1].lower()
                if file_ext in self.file_types:
                    filepath = os.path.join(root, filename)
                    total_files += 1
                    matches, replacements = self.process_file(filepath)
                    total_matches += matches
                    total_replacements += replacements

        print("\n" + "=" * 60)
        print("替换完成!")
        print(f"扫描文件总数: {total_files}")
        print(f"找到匹配的文件数: {total_matches}")
        print(f"实际替换次数: {total_replacements}")

    def process_file(self, filepath: str) -> tuple[int, int]:
        """
        处理单个文件，搜索并替换内容

        :param filepath: 文件路径
        :return: (匹配次数, 替换次数) 的元组
        """
        try:
            with open(filepath, 'r', encoding='utf-8') as f:
                content = f.read()
        except UnicodeDecodeError:
            try:
                with open(filepath, 'r', encoding='gbk') as f:
                    content = f.read()
            except Exception as e:
                print(f"无法读取文件 {filepath}: {str(e)}")
                return 0, 0
        except Exception as e:
            print(f"无法读取文件 {filepath}: {str(e)}")
            return 0, 0

        # 搜索匹配
        matches = list(re.finditer(self.search_pattern, content, flags=self.flags))
        if not matches:
            return 0, 0

        print(f"\n在文件 {filepath} 中找到 {len(matches)} 处匹配")

        # 显示匹配内容
        for i, match in enumerate(matches, 1):
            start_line = content.count('\n', 0, match.start()) + 1
            end_line = content.count('\n', 0, match.end()) + 1
            context_start = max(0, match.start() - 20)
            context_end = min(len(content), match.end() + 20)
            context = content[context_start:context_end].replace('\n', ' ')

            print(f"匹配 {i}: 行 {start_line}-{end_line}")
            print(f"上下文: ...{context}...")

        # 执行替换
        new_content, replacements = re.subn(
            self.search_pattern,
            self.replace_with,
            content,
            flags=self.flags
        )

        if replacements > 0:
            print(f"将替换 {replacements} 处")

            if not self.dry_run:
                try:
                    # 创建备份文件
                    backup_path = filepath + '.bak'
                    if os.path.exists(backup_path):
                        os.remove(backup_path)
                    os.rename(filepath, backup_path)

                    # 写入新内容
                    with open(filepath, 'w', encoding='utf-8') as f:
                        f.write(new_content)

                    print(f"已更新文件并创建备份: {backup_path}")
                except Exception as e:
                    print(f"替换文件 {filepath} 时出错: {str(e)}")
                    return len(matches), 0

        return len(matches), replacements


def main():
    parser = argparse.ArgumentParser(
        description="文件内容搜索与替换工具",
        formatter_class=argparse.ArgumentDefaultsHelpFormatter
    )
    parser.add_argument("directory", help="要搜索的根目录")
    parser.add_argument("-e", "--extensions", nargs='+', required=True,
                        help="要处理的文件扩展名(如 .txt .py)")
    parser.add_argument("-s", "--search", required=True,
                        help="要搜索的正则表达式模式")
    parser.add_argument("-r", "--replace", required=True,
                        help="替换内容")
    parser.add_argument("--dry-run", action="store_true",
                        help="只显示将要做的更改而不实际修改文件")
    parser.add_argument("--case-sensitive", action="store_true",
                        help="区分大小写的搜索")

    args = parser.parse_args()

    try:
        replacer = FileContentReplacer(
            root_dir=args.directory,
            file_types=args.extensions,
            search_pattern=args.search,
            replace_with=args.replace,
            dry_run=args.dry_run,
            case_sensitive=args.case_sensitive
        )
        replacer.run()
    except Exception as e:
        print(f"发生错误: {str(e)}")
        exit(1)


if __name__ == "__main__":
    main()
